import 'dart:async';
import 'dart:io';
import 'dart:typed_data';

import 'package:im_app/im/util/Md5Util.dart';
import 'package:logger/logger.dart';
import 'package:uuid/uuid.dart';

import '../binary/BufferUtil.dart';
import '../binary/ByteBuf.dart';
import '../socket/dto/base/Resp.dart';
import '../util/DigestUtils.dart';
import '../util/ImConstant.dart';
import 'ImSocket.dart';
import 'UploadAndDownloadDto.dart';

const fileUploadPSize = ImConstant.FILE_UPLOAD_P_SIZE;
const String TAG = 'FileUploadSocket';

typedef void UploadCallback(FileUploadResult fileUploadResult);

class FileUploadSocket extends ImSocket {

  final _logger = Logger(printer: PrettyPrinter(),);

  String tenantCode;
  String userId;
  int messageId;
  String messageServerId;
  String fileOriginName;
  String filePath;
  String ip;
  int port;
  ErrorCallback errorCallback = (ErrorResult errorResult)=>{};
  ReceiveData receiveData = (List<int> data)=> {}; // 接受数据回调

  UploadCallback uploadListener = (FileUploadResult uploadResult)=>{};

  ByteBuf byteBuf = ByteBuf(fileUploadPSize);
  RandomAccessFile? randomAccessFile = null;
  int fileSize = 0;
  int uploadSize = 0;
  bool isFileUploadResponse = false;
  int uploadFileStartTime = 0;
  bool isUpdateSuccess = false;

  Completer<Object> completer = Completer();

  FileUploadSocket(this.tenantCode, this.userId, this.messageId, this.messageServerId, this.fileOriginName,
      this.filePath, this.ip, this.port) : super(ip: ip, port: port) {
    initData(receiveSocketData, socketErrorCallback);
  }

  setUploadListener(UploadCallback uploadListener) {
    this.uploadListener = uploadListener;
  }

  Future<Object> sendFileInfoReq(int timeout) async {
    try {
      var fileUrl = filePath;

      var md5 = fileSha512(fileUrl);
      _logger.i("$TAG, '[Upload file md5]', $md5");

      var file = File(fileUrl);
      fileSize = await file.length();
      _logger.i("$TAG, [Upload file size], $fileSize");
      if (fileSize < 1) {
        var er = new ExceptionResp(-1, '文件大小为0');
        return er;
      }

      var req = FileInfoReq();
      var _uuid = const Uuid();
      var localSeq = _uuid.v1();
      req.localSeq = localSeq;
      req.tenantCode = tenantCode;
      req.userId = this.userId;
      req.fileServerId = this.messageServerId;
      req.fileUrl = fileUrl!;
      req.fileName = this.fileOriginName;
      req.fileMd5 = md5!.toLowerCase();
      req.fileSize =fileSize;

      var content = "${req.fileUrl}_${req.fileSize}_${req.fileServerId}";
      var authCode = DigestUtils.sha1(content);
      req.fileAuthCode = authCode;

      ByteBuf byteBuf = ByteBuf(1024);
      byteBuf.writeByte(10);  // 文件上传
      byteBuf.writeInt(0);
      BufferUtil.encode2(byteBuf, req);
      var length = byteBuf.writeOffset - 5;
      byteBuf.setInt(1, length);

      var arrayBuffer = byteBuf.getArrayBuffer();
      await sendData(arrayBuffer);
      return await completer.future.timeout(Duration(seconds: timeout));
    } catch(error, stackTrace) {
      _logger.e('$TAG, [Upload send size read end]', error: error, stackTrace: stackTrace, time: DateTime.now());
      completer.completeError('发送文件信息失败', stackTrace);
      return Future.value(null);
    }
  }

  sendFile() async {
    uploadFileStartTime = DateTime.now().millisecond;
    var fileUrl = filePath;
    var file = File(fileUrl);
    int batchSize = 10 * 1024;
    int readPosition = 0;
    Uint8List data = Uint8List(batchSize);
    try {
      randomAccessFile = await file.open(mode: FileMode.read);

      await randomAccessFile?.setPosition(readPosition);
      var length = await randomAccessFile!.readInto(data, 0);
      sendData(data.sublist(0, length));
      readPosition += length;
      _logger.i("$TAG, [Upload send size], $uploadSize/$readPosition/$fileSize");

      while(readPosition < fileSize) {
        await randomAccessFile!.setPosition(readPosition);
        length = await randomAccessFile!.readInto(data, 0);
        sendData(data.sublist(0, length));
        readPosition += length;
        _logger.i("$TAG, [Upload send size], $uploadSize/$readPosition/$fileSize");
      }

      var time = ((DateTime.now().millisecond - uploadFileStartTime)/1000).floor();
      _logger.i("$TAG, [Upload send size read end], $uploadSize/$readPosition/$fileSize/time: ${time}");
    } catch (error) {
      _logger.e('[Upload send file error]', error: error);
      _closeFile();
    }
  }

  receiveSocketData(List<int> data) async {
    var arrayBuf = Uint8List.fromList(data);
    byteBuf.writeArrayBuffer(arrayBuf);
    while(true) {
      var readableSize = byteBuf.readableSize();
      if (readableSize < 5) break;

      byteBuf.markReadIndex();
      var length = byteBuf.readInt()!;
      readableSize = byteBuf.readableSize();
      if (readableSize < length + 1) {
        byteBuf.resetMarkReadIndex();
        break;
      }
      var type = byteBuf.readByte();
      if (type == 1) { // 文件上传请求回应
        await doFileUploadResultBusiness();
      } else if (type == 10) { // 数据报告
        await doUploadReport();
      } else if (type == 20) { // 数据上传成功
        _closeFile();
        await doUploadSuccess();
      } else if (type == -1) { // 数据异常
        await doServerException();
      } else {
        _logger.e("$TAG, [Upload receive error type], $type");
        uploadListener(FileUploadResult(fileSize, uploadSize, false, "获取异常类型:$type"));
        _closeFile();
      }
    }
    byteBuf.shake();
  }

  socketErrorCallback(ErrorResult errorResult) {
    uploadListener(FileUploadResult(fileSize, uploadSize, false, errorResult.errorMsg));
  }

  doFileUploadResultBusiness() {
    _logger.i("$TAG, [Upload requestResult], begin");
    try {
      var okResponse = OkResp(coder: -1, successDataTime: DateTime.now(), localSeq: '');
      BufferUtil.decode(okResponse, byteBuf);

      var localSeq = okResponse.localSeq;
      if (!(localSeq == null)) {
        completer.complete(okResponse);
        isFileUploadResponse = true;
      }
    } catch (error, stackTrace) {
      _logger.e("$TAG, [Upload requestResult error]", error: error, stackTrace: stackTrace);
    }
  }

  doUploadSuccess() async {
    _logger.i("$TAG, [Upload success], begin");
    try {
      var okResponse = OkResp(coder: -1, successDataTime: DateTime.now(), localSeq: '');
      BufferUtil.decode(okResponse, byteBuf);
      if (okResponse.coder == 10) {
        _logger.i("$TAG, [Upload success], decode success");
        if (!isUpdateSuccess) {
          isUpdateSuccess = true;
          uploadListener(FileUploadResult(fileSize, fileSize, true, ''));
        }
      } else {
        uploadListener(FileUploadResult(fileSize, 0, false, '服务器端数据返回错误'));
      }
    } catch (error, stackTrace) {
      _logger.e("$TAG, [Upload success error]", error: error, stackTrace: stackTrace);
    }
  }

  doServerException() async {
    _logger.i("$TAG, [Upload exception], begin");
    try {
      var exceptionResp = ExceptionResp(-1, '');
      BufferUtil.decode(exceptionResp, byteBuf);
      _logger.e("$TAG, [Upload receive data error], ${exceptionResp.errorStatus}: ${exceptionResp.errorMsg}");
      if (!isFileUploadResponse) {
        completer.complete(exceptionResp);
      }
      String errorMsg = exceptionResp!.errorMsg??(exceptionResp.errorStatus.toString());
      uploadListener(FileUploadResult(fileSize, uploadSize, false, errorMsg));
      return;
    } catch (error, stackTrace) {
      _logger.e("$TAG, [Upload exception error]", error: error, stackTrace: stackTrace);
    }
  }

  doUploadReport() async {
    _logger.i("$TAG, [Upload report], begin");
    try {
      var fileUploadReport = FileUploadReport();
      BufferUtil.decode(fileUploadReport, byteBuf);
      _logger.i("$TAG, [Upload report], ${fileUploadReport.uploadSize} / $fileSize");
      uploadSize = fileUploadReport.uploadSize;
      if (fileSize == uploadSize) {
        if (!isUpdateSuccess) {
          isUpdateSuccess = true;
          uploadListener(FileUploadResult(fileSize, uploadSize, fileSize == uploadSize, ''));
        }
      } else {
        uploadListener(FileUploadResult(fileSize, uploadSize, false, ''));
      }

    } catch (error, stacktrace) {
      _logger.e("$TAG, [Upload report error]", error: error, stackTrace: stacktrace);
    }
  }

  Future<void> close() async {
    _closeFile();
    super.close();
  }

  void _closeFile() {
    randomAccessFile?.close();
    randomAccessFile = null;
  }

}